using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Xml;
using ArcEngine.Graphics;
using Tao.OpenGl;

namespace ArcEngine.Resources
{
	/// <summary>
	///  Collection of Tile
	/// </summary>
	public class TileSet : ResourceBase
	{

		#region constructor
		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="name">Name of the bank</param>
		public TileSet(string name) : base(name)
		{
			tiles = new Dictionary<int, Tile>();
		}


		#endregion

		
		#region Properties
		/// <summary>
		/// List of all tiles in the TileSet
		/// </summary>
		public List<int> Tiles
		{
			get
			{
				List<int> list = new List<int>();

				foreach (int id in tiles.Keys)
					list.Add(id);

				return list;
			}
		}
		Dictionary<int, Tile> tiles;

		/// <summary>
		/// Name of the texture
		/// </summary>
		[CategoryAttribute("TileSet")]
		[DescriptionAttribute("Name of the texture to use")]
		[TypeConverter(typeof(TextureEnumerator))]
		public string TextureName
		{
			set
			{
				textureName = value;

				if (texture != null) texture.UnlockResource();
				texture = ResourceManager.GetTexture(value);
				if (texture != null) texture.LockResource();
			}

			get
			{
				return textureName;
			}
		}
		string textureName;

		/// <summary>
		/// Gets / sets the texture
		/// </summary>
		[Browsable(false)]
		public Texture Texture
		{
			get { return texture; }
			set
			{
				texture = value;
			}
		}
		Texture texture;


		/// <summary>
		/// Returns the number of tiles in the bank
		/// </summary>
		[CategoryAttribute("TileSet")]
		[DescriptionAttribute("Number of tile in this bank")]
		public int Count
		{
			get
			{
				return tiles.Count;
			}
		}




		/// <summary>
		/// Gets/sets the zoom factor of the tiles
		/// </summary>
		[CategoryAttribute("TileSet")]
		[DescriptionAttribute("Zoom of the tiles")]		
		public SizeF Zoom
		{
			get
			{
				return zoom;
			}
			set
			{
				zoom = value;
			}
		}
		SizeF zoom = new SizeF(1.0f, 1.0f);

									

		#endregion


		#region	IO operations
		///
		///<summary>
		/// Save the collection to a xml node
		/// </summary>
		///
		public override bool Save(XmlWriter xml)
		{
			if (xml == null)
				return false;


			xml.WriteStartElement("tileset");
			xml.WriteAttributeString("name", Name);

			base.SaveComment(xml);


			if (textureName != null)
			{
				xml.WriteStartElement("texture");
				xml.WriteAttributeString("name", textureName);
				xml.WriteEndElement();
			}

			// Loops throughs cells
			foreach(KeyValuePair<int, Tile> val in tiles)
			{
				xml.WriteStartElement("tile");

				xml.WriteAttributeString("id", val.Key.ToString());

				xml.WriteStartElement("rectangle");
				xml.WriteAttributeString("x", val.Value.Rectangle.X.ToString());
				xml.WriteAttributeString("y", val.Value.Rectangle.Y.ToString());
				xml.WriteAttributeString("width", val.Value.Rectangle.Width.ToString());
				xml.WriteAttributeString("height", val.Value.Rectangle.Height.ToString());
				xml.WriteEndElement();

				xml.WriteStartElement("hotspot");
				xml.WriteAttributeString("x", val.Value.HotSpot.X.ToString());
				xml.WriteAttributeString("y", val.Value.HotSpot.Y.ToString());
				xml.WriteEndElement();

				xml.WriteStartElement("collisionbox");
				xml.WriteAttributeString("x", val.Value.CollisionBox.X.ToString());
				xml.WriteAttributeString("y", val.Value.CollisionBox.Y.ToString());
				xml.WriteAttributeString("width", val.Value.CollisionBox.Width.ToString());
				xml.WriteAttributeString("height", val.Value.CollisionBox.Height.ToString());
				xml.WriteEndElement();

				xml.WriteEndElement();
			}


			xml.WriteEndElement();

			return true;
		}


		/// <summary>
		/// Loads the collection from a xml node
		/// </summary>
		/// <param name="xml"></param>
		/// <returns></returns>
		public override bool Load(XmlNode xml)
		{

			// Process datas
			XmlNodeList nodes = xml.ChildNodes;
			foreach (XmlNode node in nodes)
			{
				if (node.NodeType == XmlNodeType.Comment)
				{
					base.LoadComment(node);
					continue;
				}


				switch (node.Name.ToLower())
				{
					// Texture
					case "texture":
					{
						TextureName = node.Attributes["name"].Value;
					}
					break;



					// Found a tile to add
					case "tile":
					{
						Tile tile = new Tile();
						int tileid = Int32.Parse(node.Attributes["id"].Value);


						foreach (XmlNode subnode in node)
						{
							switch (subnode.Name.ToLower())
							{
								case "rectangle":
								{
									Rectangle rect = Rectangle.Empty;
									rect.X = Int32.Parse(subnode.Attributes["x"].Value);
									rect.Y = Int32.Parse(subnode.Attributes["y"].Value);
									rect.Width = Int32.Parse(subnode.Attributes["width"].Value);
									rect.Height = Int32.Parse(subnode.Attributes["height"].Value);

									tile.Rectangle = rect;
								}
								break;

								case "collisionbox":
								{
									Rectangle rect = Rectangle.Empty;
									rect.X = Int32.Parse(subnode.Attributes["x"].Value);
									rect.Y = Int32.Parse(subnode.Attributes["y"].Value);
									rect.Width = Int32.Parse(subnode.Attributes["width"].Value);
									rect.Height = Int32.Parse(subnode.Attributes["height"].Value);

									tile.CollisionBox = rect;
								}
								break;

								case "hotspot":
								{
									Point hotspot = Point.Empty;
									hotspot.X = int.Parse(subnode.Attributes["x"].Value);
									hotspot.Y = int.Parse(subnode.Attributes["y"].Value);

									tile.HotSpot = hotspot;
								}
								break;

								default:
								{
									Log.Send(new LogEventArgs(LogLevel.Warning, "TileSet : Unknown node element found (" + node.Name + ")", null));
								}
								break;
							}
						}



						// Add the tile
						tiles[tileid] = tile;


					}
					break;

					// Auto generate tiles
					case "generate":
					{
						int width = Int32.Parse(node.Attributes["width"].Value);
						int height = Int32.Parse(node.Attributes["height"].Value);
						int start = Int32.Parse(node.Attributes["start"].Value);

						int x = 0;
						int y = 0;
						for (y = 0; y < texture.Size.Height; y += height)
							for (x = 0; x < texture.Size.Width; x += width)
							{
								tiles[start++] = new Tile(new Rectangle(x, y, width, height), Point.Empty);
							}
					}
					break;

					default:
					{
						base.Load(node);
					}
					break;
				}
			}

			return true;
		}

		#endregion


		#region Tiles management

		/// <summary>
		/// Add a new tile to the bank
		/// </summary>
		///<param name="id"></param>
		public Tile AddTile(int id) 
		{
			Tile tile = new Tile();
			tiles[id] = tile;



			// Gather tile colors
			//tile.Data = texture.GetData(tile.Rectangle);


			return tile;
		}


		/// <summary>
		/// Returns a list of all availble tiles
		/// </summary>
		/// <returns></returns>
		public List<int> GetTiles()
		{
			List<int> list = new List<int>();

			foreach (int id in tiles.Keys)
				list.Add(id);

			list.Sort();
			return list;
		}

		/// <summary>
		/// Return the information for a tile
		/// </summary>
		/// <param name="id"></param>
		/// <returns></returns>
		public Tile GetTile(int id)
		{
			if (tiles.ContainsKey(id))
				return tiles[id];
			else
				return null;
/*
			try
			{
				return Tiles[id];
			}
			catch (Exception)
			{
				return null;
			}
*/
		}

/*
		/// <summary>
		/// Returns the color data of a tile
		/// </summary>
		/// <param name="id">Tile ID</param>
		/// <returns></returns>
		public void GetCollisionMask(int id)
		{
			if (id < 0 || id > Tiles.Count)
				return;


			Tile tile = GetTile(id);
			if (tile == null)
				return;

			// If colors already gathered
			if (tile.CollisionMask != null)
				return;

			

			//tile.Data = new Color[tile.Rectangle.Width * tile.Rectangle.Height];
			//IntPtr pixels = IntPtr.Zero;
			//Gl.glGetTexImage(Gl.GL_TEXTURE_2D, 0, Gl.GL_ALPHA, Gl.GL_BYTE, pixels);

			//System.Runtime.InteropServices.Marshal.Copy(pixels, tile.Data, 0, tile.Data.Length);

			return;
		}

*/
/*
		/// <summary>
		/// Gets collision mask for every tiles
		/// </summary>
		public bool GetCollisionMasks()
		{
			if (texture == null)
				return false;

			// Lock texture
			LockedBitmap lbm = texture.LockBits(System.Drawing.Imaging.ImageLockMode.ReadOnly);


			foreach (Tile tile in Tiles.Values)
			{
				// Collision mask
				if (tile.CollisionMask == null || tile.CollisionMask.Length != tile.Size.Width * tile.Size.Height)
				{
					tile.CollisionMask = new bool[(int)tile.Size.Width, (int)tile.Size.Height];
				}

				for (int y = (int)tile.Rectangle.Top; y < tile.Rectangle.Top + tile.Rectangle.Height; y++)
				{
					for (int x = (int)tile.Rectangle.Left; x < tile.Rectangle.Left + tile.Rectangle.Width; x++)
					{
						tile.CollisionMask[x - (int)tile.Rectangle.Left, y - (int)tile.Rectangle.Top] = lbm.Data[y * lbm.Width * 4 + x * 4 + 3] != 0 ? true : false;
					}
				}	
			}

			// Unlock texture
			texture.UnlockBits(lbm);


			return true;
		}
*/

		/// <summary>
		/// Removes a tile
		/// </summary>
		/// <param name="id"></param>
		public void Remove(int id)
		{
			if (GetTile(id) == null)
				return;
			tiles.Remove(id);
		}


		/// <summary>
		/// Remove all tiles
		/// </summary>
		public void Clear()
		{
			tiles.Clear();
		}

		#endregion


		#region Rendering


		/// <summary>
		/// Binds the icon's texture
		/// </summary>
		public void Bind()
		{
			Video.Texture = texture;
		}


		/// <summary>
		/// Drawss a tile on the screen
		/// </summary>
		/// <param name="id">Tile ID</param>
		/// <param name="pos">Position on the screen</param>
		public void Draw(int id, Point pos)
		{
			Bind();
			Gl.glBegin(Gl.GL_QUADS);
			RawDraw(id, pos);
			Gl.glEnd();
		}


		/// <summary>
		/// Draws a tile on the screen and flip it
		/// </summary>
		/// <param name="id">Tile ID</param>
		/// <param name="pos">Location of the tile on the screen</param>
		/// <param name="vflip">Verticaly flip the texture</param>
		/// <param name="hflip">Horizontaly flip the texture</param>
		public void Draw(int id, Point pos, bool hflip, bool vflip)
		{

			Tile tile = GetTile(id);
			if (tile == null)
				return;

			Bind();
			Gl.glBegin(Gl.GL_QUADS);

			Point start = new Point(
				pos.X - (int)(tile.HotSpot.X * Zoom.Width),
				pos.Y - (int)(tile.HotSpot.Y * Zoom.Height)
				);

			Rectangle dst = new Rectangle(start,
				new Size(
					(int)(tile.Rectangle.Width * zoom.Width),
					(int)(tile.Rectangle.Height * zoom.Height))
					);


			Rectangle tex = tile.Rectangle;

			if (hflip)
			{
				tex.X = tile.Rectangle.Width + tile.Rectangle.X;
				tex.Width = -tile.Rectangle.Width;
			}

			if (vflip)
			{
				tex.Y = tile.Rectangle.Height + tile.Rectangle.Y;
				tex.Height= -tile.Rectangle.Height;
			}



			Video.RawBlit(dst, tex);

			Gl.glEnd();
		}


		/// <summary>
		/// Draws a tile on the screen and resize it
		/// </summary>
		/// <param name="id">Tile ID</param>
		/// <param name="rect">Rectangle on the screen</param>
		/// <param name="mode">Rendering mode</param>
		public void Draw(int id, Rectangle rect, TextureLayout mode)
		{
			Tile tile = GetTile(id);

			// Display mode
			switch (mode)
			{
				default:
				{
					Draw(id, rect.Location);
				}
				break;


				case TextureLayout.Stretch:
				{
					Video.Blit(rect, tile.Rectangle);
				}
				break;


				case TextureLayout.Center:
				{
					Point pos = new Point(
						(rect.Width - tile.Size.Width) / 2,
						(rect.Height - tile.Size.Height) / 2
						);
					pos.Offset(rect.Location);
					Draw(id, pos);
				}
				break;


				case TextureLayout.Tile:
				{
					Video.ScissorZone = rect;
					Video.Scissor = true;

					for(int y = rect.Location.Y; y < rect.Location.Y + rect.Height; y += tile.Size.Height)
						for (int x = rect.Location.X; x < rect.Location.X + rect.Width; x += tile.Size.Width)
						{
							Draw(id, new Point(x, y));
						}

					Video.Scissor = false;
				}
				break;

				case TextureLayout.Zoom:
				{
				}
				break;
			}
		}


		/// <summary>
		/// Draws a tile on the screen
		/// </summary>
		/// <param name="id">Tile ID</param>
		/// <param name="loc">Location of the tile on the screen</param>
		public void RawDraw(int id, Point loc)
		{
			Tile tile = GetTile(id);
			if (tile == null)
				return;

			Point start = new Point(
				loc.X - (int) (tile.HotSpot.X * Zoom.Width),
				loc.Y - (int) (tile.HotSpot.Y * Zoom.Height)
				);
			Rectangle dst = new Rectangle(start, 
				new Size(
					(int) (tile.Rectangle.Width * zoom.Width),
					(int) (tile.Rectangle.Height * zoom.Height))
					);

			Video.RawBlit(dst, tile.Rectangle);

		}


		#endregion


	}


}
